# 👉 Intersection Observer 用法详解

# 什么是 Intersection Observer

Intersection Observer 是浏是浏览器提供的一个 异步 API,用来异步检测某个目标元素是否进入或离开了另一个元素(或视口)的可视区域。常用于实现懒加载、元素曝光统计等场景。它的主要优点是:

  • 不需要监听滚动事件 + 手动计算位置(节省性能)。
  • 异步回调,不会和主线程渲染抢资源。
  • 可以设置 阈值,灵活控制触发时机。

在没有 Intersection Observer API 前,如果需要判断一个元素是否进入了视口,一般采用的方法:

  • 监听 scroll 事件,在事件回调中判断元素是否进入了视口。 该方法的原理是监听 scroll 事件,在事件回调中判断元素是否进入了视口,该方法的缺点是会频繁触发 scroll 事件,影响性能。

    window.addEventListener('scroll', () => {
      const scrollBox = document.querySelector('#ad');
      const rect = scrollBox.getBoundingClientRect();
      if (rect.top < window.innerHeight && rect.bottom >= 0) {
        // 元素进入了视口
      }
    
      // 或者获取scroll的位置信息,以下虚拟代码
      const scrollTop = scrollBox.scrollTop // box滚动了多少
      const containerHeight = scrollBox.clientHeight // box在视口的高度
      const scrollHeight = scrollBox.scrollHeight // box的实际高度
      if (!this.loading && scrollTop + containerHeight >= scrollHeight - 20) {
          this.loadMore()
      }
    })
    
  • 使用 getBoundingClientRect() 方法获取元素的位置信息,判断元素是否进入了视口。 该方法返回一个 DOMRect 对象,包含元素的位置信息,如 top、bottom、left、right、width、height 等。该方法的缺点是每次调用都需要计算元素的位置信息,性能受到影响。

    const element = document.querySelector('#ad');
    const rect = element.getBoundingClientRect();
    if (rect.top < window.innerHeight && rect.bottom >= 0) {
      // 元素进入了视口
    }
    

# 基本用法

const observer = new IntersectionObserver(callback, option);

observer.observe(element);

observer.unobserve(element);

observer.disconnect();

# 参数详解

# callback

callback 回调是 IntersectionObserver 构造函数的第一个参数,当目标元素进入/离开可视区域时触发

回调参数是一个 entries 数组,每一项都是 IntersectionObserverEntry,包含元素的可见性信息。回调函数的参数:

  • entries:一个数组,包含所有被观察元素的 IntersectionObserverEntry 对象。
  • observer:IntersectionObserver 实例。

IntersectionObserverEntry 对象包含以下属性:

  • isIntersecting:元素是否进入了视口。
  • intersectionRatio:元素可见比例(0~1)。
  • target:被观察的元素。
  • time:触发回调的时间。
  • boundingClientRect:元素大小和位置。
  • intersectionRect:元素可见部分的位置和大小。
  • rootBounds:根元素的位置信息,包含 top、bottom、left、right、width、height 等属性。
(entries, observer) => {

  entries.forEach(entry => {
    console.log(entry.isIntersecting) // 是否进入可视区域
    console.log(entry.intersectionRatio) // 可见比例(0~1)
    console.log(entry.boundingClientRect) // 元素大小和位置
    console.log(entry.intersectionRect) // 可见部分的位置和大小
    console.log(entry.target) // 被观察的元素
    if (entry.isIntersecting && intersectionRect > 0) {
      // 元素进入了视口
    }
  })
}

# option

const option = {
  root: null,
  rootMargin: '0px',
  threshold: 1.0
}

option 是构造函数的第二个参数,包含以下属性:

  • root:用于计算交叉状态的根元素,默认为视口。
  • rootMargin:根元素的外边距,类似 CSS 的 margin。
  • threshold:触发 callback 的阈值(0~1),表示目标元素可见比例。

# Intersection Observer 工具函数封装

封装一个通用的 Intersection Observer 工具函数,用于监听元素是否进入视口以及处理回调。

function createIntersectionObserver(target, callback, options) {
  const observer = new IntersectionObserver(callback, options);
  observer.observe(target);
  return observer;
}

# 工具函数使用示例

# 元素曝光统计

const observer = createIntersectionObserver(document.querySelector('#ad'), (entry) => {
  if (entry.isIntersecting) {
    // 统计曝光
    console.log('广告曝光:', entry.target.dataset.adid)
    // 上报一次即可
    observer.unobserve(target);
  }
});

# 无限滚动/分页加载

<ul id="list"></ul>
<div id="sentinel"></div>
const loadingDiv = document.querySelector('.sentinel');

createIntersectionObserver(loadingDiv, (entry) => {
  if (entry.isIntersecting) {
    // 加载数据
    loadMoreData();
  }
});

# 图片懒加载

createIntersectionObserver(document.querySelector('img[data-src]'), (entry) => {
  if (entry.isIntersecting) {
    entry.target.src = entry.target.dataset.src;
    observer.unobserve(entry.target);
  }
});